﻿using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;

/// <summary>
/// 用代码直接绘制场景中的大量Sprite，例如轮胎印（用场景3D相机）;
/// 用法：
/// </summary>
public class SpriteDrawingManager : MonoBehaviour
{
    internal struct SpriteKey
    {
        public Sprite sprite;
        public Material material;
    }
    
    class SpriteKeyComparer : IEqualityComparer<SpriteKey>
    {
        public bool Equals(SpriteKey x, SpriteKey y)
        {
            return x.sprite == y.sprite && x.material == y.material;
        }
    
        public int GetHashCode(SpriteKey obj)
        {
            return obj.sprite.GetHashCode() ^ obj.material.GetHashCode();
        }
    }

    public struct SpriteInst
    {
        internal SpriteKey key;
        internal SpriteBatch batch;
        internal int idx;

        public Vector3 position
        {
            get => batch.data[idx].position;
            set
            {
                var data = batch.data[idx];
                data.position = value;
                batch.data[idx] = data;
            }
        }
        
        public Quaternion rotation
        {
            get => batch.data[idx].rotation;
            set
            {
                var data = batch.data[idx];
                data.rotation = value;
                batch.data[idx] = data;
            }
        }
        
        public Vector3 scale
        {
            get => batch.data[idx].scale;
            set
            {
                var data = batch.data[idx];
                data.scale = value;
                batch.data[idx] = data;
            }
        }
        
        public Color color
        {
            get => batch.data[idx].color;
            set
            {
                var data = batch.data[idx];
                data.color = value;
                batch.data[idx] = data;
            }
        }

        internal bool show
        {
            get => batch.data[idx].show > 0;
            set
            {
                var data = batch.data[idx];
                data.show = (byte) (value ? 1 : 0);
                batch.data[idx] = data;
            }
        }

        internal float fadeStartTime
        {
            get => batch.data[idx].fadeStartTime;
            set
            {
                var data = batch.data[idx];
                data.fadeStartTime = value;
                batch.data[idx] = data;
            }
        }
        
        internal float fadeDuration
        {
            get => batch.data[idx].fadeDuration;
            set
            {
                var data = batch.data[idx];
                data.fadeDuration = value;
                batch.data[idx] = data;
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct SpriteInstData
    {
        public byte show;
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 scale;
        public Color color;
        public float fadeStartTime;
        public float fadeDuration;
    }
    
    internal class SpriteBatch
    {
        public Mesh batchMesh;
        public NativeList<SpriteInstData> data = new NativeList<SpriteInstData>(reserve, Allocator.Persistent);
        public NativeList<int> pool = new NativeList<int>(reserve, Allocator.Persistent);
    }
    
    CommandBuffer m_CommandBuffer;
    public Camera m_Camera;
    
    const int reserve = 128;
    public bool IsInit;
    
    readonly Dictionary<Shader, Material> m_MaterialCache = new Dictionary<Shader, Material>();
    readonly Dictionary<SpriteKey, SpriteBatch> m_AllInsts = new Dictionary<SpriteKey, SpriteBatch>(new SpriteKeyComparer());
    readonly List<Vector3> cacheVertices = new List<Vector3>(reserve * 4);
    readonly List<Vector2> cacheUVs = new List<Vector2>(reserve * 4);
    readonly List<Color> cacheColors = new List<Color>(reserve * 4);
    readonly List<int> cacheIndices = new List<int>(reserve * 6);
    
    static SpriteDrawingManager _inst;
    public bool useBurst;
    public static SpriteDrawingManager Inst
    {
        get
        {
            if (_inst == null)
            {
                var go = new GameObject(nameof(SpriteDrawingManager));
                _inst = go.AddComponent<SpriteDrawingManager>();
            }

            return _inst;
        }
    }
    
    public void Initialize()
    {
        IsInit = true;
        m_CommandBuffer = new CommandBuffer();
        m_CommandBuffer.name = nameof(SpriteDrawingManager);
        //m_Camera = SceneCamera.Inst.Camera;
        if (m_Camera == null)
            m_Camera = Camera.main;//大神建议不要这么用，自己做引用
        m_Camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, m_CommandBuffer);
    }

    void OnDestroy()
    {
        if (_inst == this)
        {
            _inst = null;
        }

        foreach (var item in m_AllInsts)
        {
            item.Value.data.Dispose();
            item.Value.pool.Dispose();
        }
        m_AllInsts.Clear();
        
        if (m_Camera != null)
        {
            m_Camera.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, m_CommandBuffer);
        }

        m_CommandBuffer?.Dispose();
    }
    
    public SpriteInst AcquireInst(SpriteRenderer rend, float fadeDuration = -1.0f)
    {
        // TODO:支持使用Spirte自己的Vertices、UV、Triangle
        var key = new SpriteKey {sprite = rend.sprite, material = AcquireMaterial(rend)};
        if (!m_AllInsts.TryGetValue(key, out var spriteBatch))
        {
            spriteBatch = new SpriteBatch();
            spriteBatch.batchMesh = new Mesh();
            spriteBatch.batchMesh.name = key.sprite.name;
            m_AllInsts.Add(key, spriteBatch);
        }
        
        SpriteInst inst = new SpriteInst();
        inst.key = key;
        inst.batch = spriteBatch;
        if (spriteBatch.pool.Length > 0)
        {
            inst.idx = spriteBatch.pool[0];
            spriteBatch.pool.RemoveAtSwapBack(0);
        }
        else if (spriteBatch.data.Length < 2048)
        {
            inst.idx = spriteBatch.data.Length;
            spriteBatch.data.Add(new SpriteInstData());
        }
        else
        {
            Debug.LogWarning("Do you forget to ReleaseInst?");
        }

        inst.show = true;
        inst.fadeStartTime = fadeDuration > 0 ? Time.time : 0;
        inst.fadeDuration = fadeDuration;
        return inst;
    }

    public void ReleaseInst(SpriteInst inst)
    {
        inst.show = false;
        inst.batch.pool.Add(inst.idx);
    }

    Material AcquireMaterial(SpriteRenderer rend)
    {
        var sharedMaterial = rend.sharedMaterial;
        if (!m_MaterialCache.TryGetValue(sharedMaterial.shader, out var material))
        {
            material = new Material(sharedMaterial);
            material.mainTexture = rend.sprite.texture;
            m_MaterialCache.Add(sharedMaterial.shader, material);
        }
        return material;
    }
    
    void Update()
    {
        m_CommandBuffer.Clear();
        foreach (var item in m_AllInsts)
        {
            var spriteBatch = item.Value;
            if (spriteBatch.pool.Length == spriteBatch.data.Length)
            {
                continue;
            }

            if (useBurst)
            {
                if (GenerateSpriteMesh(item.Key.sprite.bounds, spriteBatch))
                {
                    m_CommandBuffer.DrawMesh(spriteBatch.batchMesh, Matrix4x4.identity, item.Key.material);
                }
            }
            else
            {
                Matrix4x4 m = Matrix4x4.identity;
                m.SetTRS(new Vector3(0, 0, 10), Quaternion.identity, Vector3.one);
                m_CommandBuffer.DrawMesh(spriteBatch.batchMesh, m, item.Key.material);
            }
        }
    }

    unsafe bool GenerateSpriteMesh(Bounds spriteBounds, SpriteBatch spriteBatch)
    {
        SpriteInstData* pData = (SpriteInstData*)spriteBatch.data.GetUnsafePtr();
        var instCount = spriteBatch.data.Length;
        if (instCount == 0)
        {
            return false;
        }
        
        var outVertices = stackalloc Vector3[instCount * 4];
        var outColors = stackalloc Color[instCount * 4];
        var outUVs = stackalloc Vector2[instCount * 4];
        var outIndices = stackalloc int[instCount * 6];
        var outVertexCount = 0;
        var outIndexCount = 0;
        
        var job = new GenerateMeshJob();
        job.inData = pData;
        job.inCount = instCount;
        job.spriteBounds = spriteBounds;
        //job.focusPosition = SceneCamera.Inst.FollowPosition;
        job.pool = spriteBatch.pool;
        job.time = Time.time;
        job.outVertices = outVertices;
        job.outColors = outColors;
        job.outUVs = outUVs;
        job.outIndices = outIndices;
        job.outVertexCount = &outVertexCount;
        job.outIndexCount = &outIndexCount;
        job.Execute();

        if (outVertexCount == 0)
        {
            return false;
        }
        
        CopyToList(job.outVertices, cacheVertices, outVertexCount);
        CopyToList(job.outColors, cacheColors, outVertexCount);
        CopyToList(job.outUVs, cacheUVs, outVertexCount);
        CopyToList(job.outIndices, cacheIndices, outIndexCount);
            
        spriteBatch.batchMesh.Clear();
        spriteBatch.batchMesh.SetVertices(cacheVertices);
        spriteBatch.batchMesh.SetColors(cacheColors);
        spriteBatch.batchMesh.SetUVs(0, cacheUVs);
        spriteBatch.batchMesh.SetTriangles(cacheIndices, 0);

        return true;
    }
    
    unsafe void CopyToList<T>(T* data, List<T> list, int count) where T : unmanaged
    {
        list.Clear();
        for (int i = 0; i < count; i++)
        {
            list.Add(data[i]);
        }
    }
    
    [BurstCompile]
    unsafe struct GenerateMeshJob : IJob
    {
        public Bounds spriteBounds;
        public int inCount;
        public float time;
        public NativeList<int> pool;
        public Vector3 focusPosition;
        [NativeDisableUnsafePtrRestriction] public int* outVertexCount;
        [NativeDisableUnsafePtrRestriction] public int* outIndexCount;
        [NativeDisableUnsafePtrRestriction] public SpriteInstData* inData;
        [NativeDisableUnsafePtrRestriction] public Vector3* outVertices;
        [NativeDisableUnsafePtrRestriction] public Color* outColors;
        [NativeDisableUnsafePtrRestriction] public Vector2* outUVs;
        [NativeDisableUnsafePtrRestriction] public int* outIndices;
 
        public void Execute()
        {
            var idxVert = 0;
            var idxTriangle = 0;
            var min = spriteBounds.min;
            var max = spriteBounds.max;
            var v0 = min;
            var v1 = new Vector3(min.x, max.y, min.z);
            var v2 = max;
            var v3 = new Vector3(max.x, min.y, min.z);
            
            for (int i = 0; i < inCount; i++)
            {
                var data = inData[i];
                if (data.show == 0)
                {
                    continue;
                }

                // 检查是否生命期已结束
                var lifeTime = time - data.fadeStartTime;
                if (data.fadeStartTime > 0 && lifeTime > data.fadeDuration)
                {
                    // 释放到Pool里以便重用（相当于调用ReleaseInst）
                    data.show = 0;
                    inData[i] = data;
                    pool.Add(i);
                    continue;
                }

                // 视野范围外不显示
                if ((data.position - focusPosition).sqrMagnitude > 10*10)
                {
                    continue;
                }
                
                // 如果需要渐隐，则更新alpha
                var color = data.color;
                if (data.fadeStartTime > 0)
                {
                    color.a *= (1 - lifeTime / data.fadeDuration);
                }
                var rotate = Quaternion.Euler(new Vector3(90, 0, 0));
                Debug.LogWarning("timetraveller pos="+data.position+" rotate=" + data.rotation+" r_c=" + rotate);
                var trs = Matrix4x4.TRS(data.position, rotate, data.scale);
                outVertices[idxVert + 0] = trs.MultiplyPoint(v0);
                outVertices[idxVert + 1] = trs.MultiplyPoint(v1);
                outVertices[idxVert + 2] = trs.MultiplyPoint(v2);
                outVertices[idxVert + 3] = trs.MultiplyPoint(v3);
                
                outUVs[idxVert + 0] = new Vector2(0, 0);
                outUVs[idxVert + 1] = new Vector2(0, 1);
                outUVs[idxVert + 2] = new Vector2(1, 1);
                outUVs[idxVert + 3] = new Vector2(1, 0);

                outColors[idxVert + 0] = color;
                outColors[idxVert + 1] = color;
                outColors[idxVert + 2] = color;
                outColors[idxVert + 3] = color;

                outIndices[idxTriangle + 0] = idxVert + 0;
                outIndices[idxTriangle + 1] = idxVert + 1;
                outIndices[idxTriangle + 2] = idxVert + 3;
                outIndices[idxTriangle + 3] = idxVert + 1;
                outIndices[idxTriangle + 4] = idxVert + 2;
                outIndices[idxTriangle + 5] = idxVert + 3;

                idxVert += 4;
                idxTriangle += 6;
            }
            
            *outVertexCount = idxVert;
            *outIndexCount = idxTriangle;
        }
    }
}
